package com.cloudbroker.bcs.common.event;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import com.cloudbroker.bcs.common.context.RuntimeContext;
import com.cloudbroker.bcs.common.event.annotation.Trigger;

@Component
@Aspect
public class EventPublisher {
    
    private static final String CLASS_NAME = EventPublisher.class.getName();
    
    private static final Logger LOGGER = LoggerFactory.getLogger(CLASS_NAME);
    
    private static final Class<?>[] EVENT_CONSTRUCTOR_ARG_CLASSES = { Object.class };
    
    public EventPublisher() {
        LOGGER.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>> " + CLASS_NAME + " loaded");
    }
    
    public static void publishEvent(Class<? extends BasicEvent> eventClass, Object eventSource, String eventName,
            Object payload, boolean async, String... penetratedContextKeys) {
        if (null == eventSource) {
            throw new IllegalArgumentException("eventSource is required");
        }
        if (null == eventName || eventName.isEmpty()) {
            throw new IllegalArgumentException("eventName is required");
        }
        if (null == eventClass) {
            eventClass = BasicEvent.class;
        }
        publishEventNoCheck(eventClass, eventSource, eventName, payload, async, penetratedContextKeys);
    }
    
    private static void publishEventNoCheck(Class<? extends BasicEvent> eventClass, Object eventSource,
            String eventName, Object payload, boolean async, String... penetratedContextKeys) {
        try {
            // 创建事件
            BasicEvent event = createEvent(eventClass, eventSource);
            // 设置事件名称
            event.setName(eventName);
            // 设置事件负载（事件内容对象）
            event.setPayload(payload);
            // 设置事件异步标识
            event.setAsync(async);
            // 设置异步事件复制上下文的变量的key
            event.setPenetratedContextKeys(penetratedContextKeys);
            
            // 发布事件
            LOGGER.info("publishing event: " + event.briefToString());
            RuntimeContext.publishEvent(event);
        } catch (Exception e) {
            LOGGER.error("publish event failed", e);
        }
    }
    
    private static BasicEvent createEvent(Class<? extends BasicEvent> eventClass, Object eventSource)
            throws Exception {
        return eventClass.getConstructor(EVENT_CONSTRUCTOR_ARG_CLASSES).newInstance(eventSource);
    }
    
    @AfterReturning(pointcut = "@annotation(com.cloudbroker.bcs.common.event.annotation.Trigger)", returning = "retVal")
    public void publishEvent(JoinPoint jp, Object retVal) {
        try {
            // 在Spring的环境里，signature就是MethodSignature
            MethodSignature methodSignature = (MethodSignature) jp.getSignature();
            // 获取Method
            Method method = methodSignature.getMethod();
            // 符合本切面的一定有@Trigger
            Trigger triggerAnn = method.getAnnotation(Trigger.class);
            // 获取事件类
            Class<? extends BasicEvent> eventClass = triggerAnn.eventClass();
            // 获取事件源
            Object eventSource = jp.getTarget();
            // 获取事件名称
            String eventName = triggerAnn.eventName();
            // 如果eventName属性未设置（""），取value属性
            if (eventName.isEmpty()) {
                eventName = triggerAnn.value();
            }
            
            // 判断是否是异步事件
            boolean async = false;
            // 如果有@Async注解，则以其为准，否则读取@Trigger.async()
            if (null != method.getAnnotation(Async.class)) {
                async = true;
            } else {
                async = triggerAnn.async();
            }
            
            String[] penetratedContextKeys = null;
            if (async) {
                // 如果是异步事件，查看是否需要复制上下文变量
                penetratedContextKeys = triggerAnn.penetratedContextKeys();
            }
            
            // 创建并发布事件
            publishEventNoCheck(eventClass, eventSource, eventName, retVal, async, penetratedContextKeys);
        } catch (Exception e) {
            LOGGER.error("publish event failed", e);
        }
    }
    
}
